﻿using System;
using System.IO;
using Velocity4Net.Ctx;
using Velocity4Net.Errors;
using Velocity4Net.Runtime.Parser.Node;
using Velocity4Net.Util.Introspection;

namespace Velocity4Net.Runtime.Directives
{
    /*
     * Licensed to the Apache Software Foundation (ASF) under one
     * or more contributor license agreements.  See the NOTICE file
     * distributed with this work for additional information
     * regarding copyright ownership.  The ASF licenses this file
     * to you under the Apache License, Version 2.0 (the
     * "License"); you may not use this file except in compliance
     * with the License.  You may obtain a copy of the License at
     *
     *   http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing,
     * software distributed under the License is distributed on an
     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     * KIND, either express or implied.  See the License for the
     * specific language governing permissions and limitations
     * under the License.
     */


















    /**
     * Evaluates the directive argument as a VTL string, using the existing
     * context.
     *
     * @author <a href="mailto:wglass@apache.org">Will Glass-Husain</a>
     * @version $Id: Evaluate.java 1866609 2019-09-08 10:42:47Z cbrisson $
     * @since 1.6
     */
    public class Evaluate : Directive
    {

        /**
         * Return name of this directive.
         * @return The name of this directive.
         */
        public String getName()
        {
            return "evaluate";
        }

        /**
         * Return type of this directive.
         * @return The type of this directive.
         */
        public int getType()
        {
            return LINE;
        }

        /**
         * Initialize and check arguments.
         * @param rs
         * @param context
         * @param node
         * @throws TemplateInitException
         */
        public void init(RuntimeServices rs, InternalContextAdapter context,
                         Node node)
        {
            super.init(rs, context, node);

            /**
             * Check that there is exactly one argument and it is a string or reference.
             */

            int argCount = node.jjtGetNumChildren();
            if (argCount == 0)
            {
                throw new TemplateInitException(
                        "#" + getName() + "() requires exactly one argument",
                        null,
                        rsvc.getLogContext().getStackTrace(),
                        context.getCurrentTemplateName(),
                        node.getColumn(),
                        node.getLine());
            }
            if (argCount > 1)
            {
                /*
                 * use line/col of second argument
                 */

                throw new TemplateInitException(
                        "#" + getName() + "() requires exactly one argument",
                        null,
                        rsvc.getLogContext().getStackTrace(),
                        context.getCurrentTemplateName(),
                        node.jjtGetChild(1).getColumn(),
                        node.jjtGetChild(1).getLine());
            }

            Node childNode = node.jjtGetChild(0);
            if (childNode.getType() != ParserTreeConstants.JJTSTRINGLITERAL &&
                 childNode.getType() != ParserTreeConstants.JJTREFERENCE)
            {
                throw new TemplateInitException(
                        "#" + getName() + "()  argument must be a string literal or reference",
                        null,
                        rsvc.getLogContext().getStackTrace(),
                        context.getCurrentTemplateName(),
                        childNode.getColumn(),
                        childNode.getLine());
            }
        }

        /**
         * Evaluate the argument, convert to a String, and evaluate again
         * (with the same context).
         * @param context
         * @param writer
         * @param node
         * @return True if the directive rendered successfully.
         * @throws IOException
         * @throws ResourceNotFoundException
         * @throws ParseErrorException
         * @throws MethodInvocationException
         */
        public bool render(InternalContextAdapter context, TextWriter writer, Node node)
        {

            /*
             * Evaluate the string with the current context.  We know there is
             * exactly one argument and it is a string or reference.
             */

            Object value = node.jjtGetChild(0).value(context);
            String sourceText;
            if (value != null)
            {
                sourceText = value.ToString();
            }
            else
            {
                sourceText = "";
            }

            /*
             * The new string needs to be parsed since the text has been dynamically generated.
             */
            String templateName = context.getCurrentTemplateName();
            Template template = (Template)context.getCurrentResource();
            if (template == null)
            {
                template = new Template();
                template.setName(templateName);
            }
            SimpleNode nodeTree = null;

            try
            {
                nodeTree = rsvc.parse(new StringReader(sourceText), template);
            }
            catch (Exception pex)
            {
                // use the line/column from the template
                Info info = new Info(templateName, node.getLine(), node.getColumn());
                throw new ParseErrorException(pex.getMessage(), info, rsvc.getLogContext().getStackTrace());
            }

            /*
             * now we want to init and render.  Chain the context
             * to prevent any changes to the current context.
             */

            if (nodeTree != null)
            {
                context.pushCurrentTemplateName(templateName);

                try
                {
                    try
                    {
                        nodeTree.init(context, rsvc);
                    }
                    catch (TemplateInitException pex)
                    {
                        Info info = new Info(templateName, node.getLine(), node.getColumn());
                        throw new ParseErrorException(pex.getMessage(), info, rsvc.getLogContext().getStackTrace());
                    }

                    try
                    {
                        preRender(context);

                        /*
                         *  now render, and let any exceptions fly
                         */
                        nodeTree.render(context, writer);
                    }
                    catch (StopCommand stop)
                    {
                        if (!stop.isFor(this))
                        {
                            throw stop;
                        }
                    }
                    catch (ParseErrorException pex)
                    {
                        // convert any parsing errors to the correct line/col
                        Info info = new Info(templateName, node.getLine(), node.getColumn());
                        throw new ParseErrorException(pex.getMessage(), info, rsvc.getLogContext().getStackTrace());
                    }
                }
                finally
                {
                    context.popCurrentTemplateName();
                    postRender(context);
                }
                return true;
            }

            return false;
        }

    }
}